package com.ccc.boot.exception;

import com.ccc.boot.log.DBLog;
import com.ccc.boot.log.LogInfo;
import com.ccc.boot.response.ResponseCode;
import com.ccc.boot.util.LogUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.util.NestedServletException;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 全局业务异常处理器
 *
 * @author superC
 * @version 0.1.0
 * @date 2022/3/23
 */

@RestControllerAdvice
public class BusinessExceptionHandler {

    protected static final Logger logger = LoggerFactory.getLogger(BusinessExceptionHandler.class);

    /**
     * 统一处理表单绑定验证
     *
     * @param e
     * @return com.flyduck.cms.vo.ApiResult
     */
    @ExceptionHandler(BindException.class)
    public ResponseEntity<?> bindExceptionHandle(BindException e) {
        logger.error("【全局业务异常】\r\n表单绑定验证异常记录：", e);
        Map<String, Object> error = new HashMap<>(2);
        BindingResult bindingResult = e.getBindingResult();
        String msg = bindingResult.getAllErrors()
                .stream()
                .map(DefaultMessageSourceResolvable::getDefaultMessage)
                .distinct()
                .collect(Collectors.joining(","));
        error.put("code", HttpStatus.BAD_REQUEST.value());
        error.put("message", msg);
        return new ResponseEntity<>(error, HttpStatus.OK);
    }

    /**
     * 处理参数校验异常
     *
     * @param e 参数校验异常
     * @return 参数异常提示
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        logger.error("【全局业务异常】\r\n参数校验异常记录：", e);
        Map<String, Object> error = new HashMap<>(2);
        if (e.getBindingResult().getFieldError() == null) {
            error.put("code", ResponseCode.PARAM_ERROR.code());
            error.put("message", ResponseCode.PARAM_ERROR.message());
        }
        MethodArgumentNotValidException methodArgumentNotValidException = (MethodArgumentNotValidException) e;
        BindingResult bindingResult = methodArgumentNotValidException.getBindingResult();
        String msg = bindingResult.getFieldErrors()
                .stream()
                .map(FieldError::getDefaultMessage)
                .distinct()
                .collect(Collectors.joining(","));
        error.put("code", HttpStatus.BAD_REQUEST.value());
        error.put("message", msg);
        return new ResponseEntity<>(error, HttpStatus.OK);
    }


    /**
     * 处理请求参数格式的验证异常
     *
     * @param exception
     * @return com.flyduck.cms.vo.ApiResult
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public ResponseEntity<?> constraintViolationHandler(ConstraintViolationException exception) {
        logger.error("【全局业务异常】\r\n参数格异常记录：", exception);
        Map<String, Object> error = new HashMap<>(2);
        Set<ConstraintViolation<?>> violations = exception.getConstraintViolations();
        if (violations.size() > 0) {
            List<String> list = new ArrayList<>();
            for (ConstraintViolation<?> violation : violations) {
                list.add(violation.getMessage());
            }
        }
        return new ResponseEntity<>(error, HttpStatus.OK);
    }

    /**
     * SQL异常
     *
     * @param e 参数校验异常
     * @return 参数异常提示
     */
    @ExceptionHandler({SQLException.class})
    public ResponseEntity<?> handlerSQLException(SQLException e) {
        logger.error("【全局业务异常】\r\nSQL异常记录：", e);
        Map<String, Object> error = new HashMap<>(2);
        error.put("code", ResponseCode.SQL_ERROR.code());
        error.put("message", ResponseCode.SQL_ERROR.message());
        return new ResponseEntity<>(error, HttpStatus.OK);
    }

    /**
     * 处理业务异常
     *
     * @param request
     * @param e
     * @return {@link ResponseEntity<?>}
     * @date 2022/4/24 21:43
     * @author superC
     */

    @ExceptionHandler({BusinessException.class, Exception.class})
    public ResponseEntity<?> handlerException(HttpServletRequest request, Exception e) {
        Map<String, Object> error = new HashMap<>(2);

        // 加了spring security就被包装成这个exception了
        if (e instanceof NestedServletException) {
            // 业务异常
            if (((NestedServletException) e).getRootCause() instanceof BusinessException) {
                error.put("code", ((BusinessException) e).getCode());
                error.put("message", e.getMessage());
                logger.error("【全局业务异常】\r\n业务编码：{}\r\n异常记录：{}", error.get("code"), error.get("message"));
            }
            // 未知错误
            else {
                error.put("code", ResponseCode.UNKNOWN.code());
                error.put("message", ResponseCode.UNKNOWN.message());
                logger.error("【全局业务异常】\r\n异常记录：", e);
            }
        }

        // 业务异常
        else if (e instanceof BusinessException) {
            error.put("code", ((BusinessException) e).getCode());
            error.put("message", e.getMessage());
            logger.error("【全局业务异常】\r\n业务编码：{}\r\n异常记录：{}", error.get("code"), error.get("message"));
        }

        // 未知错误
        else {
            error.put("code", ResponseCode.UNKNOWN.code());
            error.put("message", ResponseCode.UNKNOWN.message());
            logger.error("【全局业务异常】\r\n异常记录：", e);
        }


        /*
        记录异常日志 TODO
         */
        LogInfo logInfo = LogUtils.getErrorLog(request, error.get("code").toString(), error.get("message").toString(), e.getMessage());
//        DBLog.getInstance().setLogService(errorLogService).offerQueue(logInfo);


        return new ResponseEntity<>(error, HttpStatus.OK);
    }


}